/*
 *
 *  *    Copyright 2020-2021 luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.core.limiter;

import com.luter.heimdall.core.cache.MapMemoryCache;
import com.luter.heimdall.core.cache.MemoryCache;
import com.luter.heimdall.core.config.ConfigManager;
import com.luter.heimdall.core.config.HeimdallProperties;
import com.luter.heimdall.core.exception.HeimdallCacheException;
import lombok.Data;
import lombok.experimental.Accessors;
import org.slf4j.Logger;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 基于 内存 缓存的 密码重试次数限制器
 * <p>
 * 场景：
 * <p>
 * 注意:不支持 普通 Map 作为缓存。
 * <p>
 * 尽量使用 guava 或者 Caffeine 等key 带有自动过期功能的缓存实现。
 *
 * @author luter
 */
@Data
@Accessors(chain = true, fluent = true)
public class MemoryCachedPasswordRetryLimiter implements PasswordRetryLimiter {
    /**
     * The constant log.
     */
    private static final transient Logger log = getLogger(MemoryCachedPasswordRetryLimiter.class);
    /**
     * 缓存
     */
    private MemoryCache<String, AtomicInteger> retryCache;

    /**
     * Instantiates a new Cached password retry limiter.
     */
    public MemoryCachedPasswordRetryLimiter() {
        this.retryCache = new MapMemoryCache<>(new ConcurrentHashMap<>(), "DEFAULT_LIMITER_MAP_CACHE");
    }

    /**
     * Instantiates a new Cached authentication retry limit.
     *
     * @param retryCache the retry cache
     */
    public MemoryCachedPasswordRetryLimiter(MemoryCache<String, AtomicInteger> retryCache) {
        if (retryCache == null) {
            throw new HeimdallCacheException("RetryCache map cannot be null");
        }
        this.retryCache = retryCache;
    }

    @Override
    public boolean overLimitWhenIncremented(String key) {
        final HeimdallProperties config = ConfigManager.getConfig();
        log.debug("[overLimitWhenIncremented]::key = [{}]", key);
        String cacheKey = config.getLimiter().getCachePrefix() + key;
        log.debug("[overLimitWhenIncremented]::cacheKey = [{}]", cacheKey);
        if (config.getLimiter().isEnabled()) {
            log.info("[overLimitWhenIncremented]:: The Retry Limiter is enabled, key = [{}],cacheKey = [{}]", key, cacheKey);
            //先拿
            AtomicInteger retryCount = retryCache.get(cacheKey);
            if (null == retryCount) {
                log.debug("[overLimitWhenIncremented]:: first try ,Initialize and set to 0, key = [{}],cacheKey = [{}]", key, cacheKey);
                retryCount = new AtomicInteger(0);
                retryCache.put(cacheKey, retryCount);
            }
            log.info("[overLimitWhenIncremented]::  key = [{}],cacheKey = [{}],retry times = [{}] ", key, cacheKey, retryCount.get());
            if (retryCount.incrementAndGet() > config.getLimiter().getAttempts()) {
                log.error("[overLimitWhenIncremented]:: CacheKey: [{}] retry times: [{}], " +
                                "exceeding the maximum number limit: [{}], lock the account", cacheKey,
                        retryCount, config.getLimiter().getAttempts());
                return true;
            }
            retryCache.put(cacheKey, retryCount);
        } else {
            log.debug("[overLimitWhenIncremented]::  The Retry Limiter is Disabled.  key = [{}]", key);
        }
        return false;
    }

    @Override
    public void release(String key) {
        retryCache.remove(ConfigManager.getConfig().getLimiter().getCachePrefix() +key);
    }

    @Override
    public int count(String key) {
        AtomicInteger retryCount = retryCache.get(ConfigManager.getConfig().getLimiter().getCachePrefix() + key);
        if (null != retryCount) {
            return retryCount.get();
        }
        return 0;
    }

    @Override
    public int availableTimes(String key) {
        return ConfigManager.getConfig().getLimiter().getAttempts() - count(key);
    }
}
